 /*******************************************************************************
  * Copyright (c) 2006, 2007 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/

 package org.eclipse.ui.ide.undo;

 import java.util.ArrayList ;
 import java.util.List ;

 import org.eclipse.core.commands.operations.IUndoContext;
 import org.eclipse.core.commands.operations.ObjectUndoContext;
 import org.eclipse.core.resources.IContainer;
 import org.eclipse.core.resources.IFile;
 import org.eclipse.core.resources.IProject;
 import org.eclipse.core.resources.IResource;
 import org.eclipse.core.resources.IResourceStatus;
 import org.eclipse.core.resources.IWorkspace;
 import org.eclipse.core.resources.IWorkspaceRoot;
 import org.eclipse.core.resources.ResourcesPlugin;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IAdaptable;
 import org.eclipse.core.runtime.IPath;
 import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.MultiStatus;
 import org.eclipse.core.runtime.NullProgressMonitor;
 import org.eclipse.core.runtime.OperationCanceledException;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.core.runtime.SubProgressMonitor;
 import org.eclipse.jface.dialogs.IDialogConstants;
 import org.eclipse.jface.dialogs.MessageDialog;
 import org.eclipse.osgi.util.NLS;
 import org.eclipse.swt.widgets.Shell;
 import org.eclipse.ui.PlatformUI;
 import org.eclipse.ui.internal.WorkbenchPlugin;
 import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin;
 import org.eclipse.ui.internal.ide.undo.ContainerDescription;
 import org.eclipse.ui.internal.ide.undo.FileDescription;
 import org.eclipse.ui.internal.ide.undo.UndoMessages;

 /**
  * WorkspaceUndoUtil defines common utility methods and constants used by
  * clients who create undoable workspace operations.
  *
  * @since 3.3
  *
  */
 public class WorkspaceUndoUtil {

     private static ObjectUndoContext tasksUndoContext;

     private static ObjectUndoContext bookmarksUndoContext;

     /**
      * Return the undo context that should be used for workspace-wide operations
      *
      * @return the undo context suitable for workspace-level operations.
      */
     public static IUndoContext getWorkspaceUndoContext() {
         return WorkbenchPlugin.getDefault().getOperationSupport()
                 .getUndoContext();
     }

     /**
      * Return the undo context that should be used for operations involving
      * tasks.
      *
      * @return the tasks undo context
      */
     public static IUndoContext getTasksUndoContext() {
         if (tasksUndoContext == null) {
             tasksUndoContext = new ObjectUndoContext(new Object (),
                     "Tasks Context"); //$NON-NLS-1$
 tasksUndoContext.addMatch(getWorkspaceUndoContext());
         }
         return tasksUndoContext;
     }

     /**
      * Return the undo context that should be used for operations involving
      * bookmarks.
      *
      * @return the bookmarks undo context
      */
     public static IUndoContext getBookmarksUndoContext() {
         if (bookmarksUndoContext == null) {
             bookmarksUndoContext = new ObjectUndoContext(new Object (),
                     "Bookmarks Context"); //$NON-NLS-1$
 bookmarksUndoContext.addMatch(getWorkspaceUndoContext());
         }
         return bookmarksUndoContext;
     }

     /**
      * Make an <code>IAdaptable</code> that adapts to the specified shell,
      * suitable for passing for passing to any
      * {@link org.eclipse.core.commands.operations.IUndoableOperation} or
      * {@link org.eclipse.core.commands.operations.IOperationHistory} method
      * that requires an {@link org.eclipse.core.runtime.IAdaptable}
      * <code>uiInfo</code> parameter.
      *
      * @param shell
      * the shell that should be returned by the IAdaptable when asked
      * to adapt a shell. If this parameter is <code>null</code>,
      * the returned shell will also be <code>null</code>.
      *
      * @return an IAdaptable that will return the specified shell.
      */
     public static IAdaptable getUIInfoAdapter(final Shell shell) {
         return new IAdaptable() {
             public Object getAdapter(Class clazz) {
                 if (clazz == Shell.class) {
                     return shell;
                 }
                 return null;
             }
         };
     }

     private WorkspaceUndoUtil() {
         // should not construct
 }

     /**
      * Delete all of the specified resources, returning resource descriptions
      * that can be used to restore them.
      *
      * @param resourcesToDelete
      * an array of resources to be deleted
      * @param monitor
      * the progress monitor to use to show the operation's progress
      * @param uiInfo
      * the IAdaptable (or <code>null</code>) provided by the
      * caller in order to supply UI information for prompting the
      * user if necessary. When this parameter is not
      * <code>null</code>, it contains an adapter for the
      * org.eclipse.swt.widgets.Shell.class
      *
      * @param deleteContent
      * a boolean indicating whether project content should be deleted
      * when a project resource is to be deleted
      * @return an array of ResourceDescriptions that can be used to restore the
      * deleted resources.
      * @throws CoreException
      * propagates any CoreExceptions thrown from the resources API
      */
     static ResourceDescription[] delete(IResource[] resourcesToDelete,
             IProgressMonitor monitor, IAdaptable uiInfo, boolean deleteContent)
             throws CoreException {
         final List exceptions = new ArrayList ();
         boolean forceOutOfSyncDelete = false;
         ResourceDescription[] returnedResourceDescriptions = new ResourceDescription[resourcesToDelete.length];
         monitor.beginTask("", resourcesToDelete.length); //$NON-NLS-1$
 monitor
                 .setTaskName(UndoMessages.AbstractResourcesOperation_DeleteResourcesProgress);
         try {
             for (int i = 0; i < resourcesToDelete.length; ++i) {
                 if (monitor.isCanceled()) {
                     throw new OperationCanceledException();
                 }
                 IResource resource = resourcesToDelete[i];
                 try {
                     returnedResourceDescriptions[i] = delete(resource,
                             new SubProgressMonitor(monitor, 1), uiInfo,
                             forceOutOfSyncDelete, deleteContent);
                 } catch (CoreException e) {
                     if (resource.getType() == IResource.FILE) {
                         IStatus[] children = e.getStatus().getChildren();
                         if (children.length == 1
                                 && children[0].getCode() == IResourceStatus.OUT_OF_SYNC_LOCAL) {
                             int result = queryDeleteOutOfSync(resource, uiInfo);

                             if (result == IDialogConstants.YES_ID) {
                                 // retry the delete with a force out of sync
 delete(resource, new SubProgressMonitor(
                                         monitor, 1), uiInfo, true,
                                         deleteContent);
                             } else if (result == IDialogConstants.YES_TO_ALL_ID) {
                                 // all future attempts should force out of
 // sync
 forceOutOfSyncDelete = true;
                                 delete(resource, new SubProgressMonitor(
                                         monitor, 1), uiInfo,
                                         forceOutOfSyncDelete, deleteContent);
                             } else if (result == IDialogConstants.CANCEL_ID) {
                                 throw new OperationCanceledException();
                             } else {
                                 exceptions.add(e);
                             }
                         } else {
                             exceptions.add(e);
                         }
                     } else {
                         exceptions.add(e);
                     }
                 }
             }
             IStatus result = createResult(exceptions);
             if (!result.isOK()) {
                 throw new CoreException(result);
             }
         } finally {
             monitor.done();
         }
         return returnedResourceDescriptions;
     }

     /**
      * Copies the resources to the given destination. This method can be called
      * recursively to merge folders during folder copy.
      *
      * @param resources
      * the resources to be copied
      * @param destination
      * the destination path for the resources, relative to the
      * workspace
      * @param resourcesAtDestination
      * A list used to record the new copies.
      * @param monitor
      * the progress monitor used to show progress
      * @param uiInfo
      * the IAdaptable (or <code>null</code>) provided by the
      * caller in order to supply UI information for prompting the
      * user if necessary. When this parameter is not
      * <code>null</code>, it contains an adapter for the
      * org.eclipse.swt.widgets.Shell.class
      * @param pathIncludesName
      * a boolean that indicates whether the specified path includes
      * the resource's name at the destination. If this value is
      * <code>true</code>, the destination will contain the desired
      * name of the resource (usually only desired when only one
      * resource is being copied). If this value is <code>false</code>,
      * each resource's name will be appended to the destination.
      * @return an array of ResourceDescriptions describing any resources that
      * were overwritten by the copy operation
      * @throws CoreException
      * propagates any CoreExceptions thrown from the resources API
      */
     static ResourceDescription[] copy(IResource[] resources, IPath destination,
             List resourcesAtDestination, IProgressMonitor monitor,
             IAdaptable uiInfo, boolean pathIncludesName) throws CoreException {

         monitor.beginTask("", resources.length); //$NON-NLS-1$
 monitor
                 .setTaskName(UndoMessages.AbstractResourcesOperation_CopyingResourcesProgress);
         List overwrittenResources = new ArrayList ();
         for (int i = 0; i < resources.length; i++) {
             IResource source = resources[i];
             IPath destinationPath;
             if (pathIncludesName) {
                 destinationPath = destination;
             } else {
                 destinationPath = destination.append(source.getName());
             }
             IWorkspaceRoot workspaceRoot = getWorkspaceRoot();
             IResource existing = workspaceRoot.findMember(destinationPath);
             if (source.getType() == IResource.FOLDER && existing != null) {
                 // The resource is a folder and it exists in the destination.
 // Copy its children to the existing destination.
 if (source.isLinked() == existing.isLinked()) {
                     IResource[] children = ((IContainer) source).members();
                     ResourceDescription[] overwritten = copy(children,
                             destinationPath, resourcesAtDestination,
                             new SubProgressMonitor(monitor, 1), uiInfo, false);
                     // We don't record the copy since this recursive call will
 // do so. Just record the overwrites.
 for (int j = 0; j < overwritten.length; j++) {
                         overwrittenResources.add(overwritten[j]);
                     }
                 } else {
                     // delete the destination folder, copying a linked folder
 // over an unlinked one or vice versa. Fixes bug 28772.
 ResourceDescription[] deleted = delete(
                             new IResource[] { existing },
                             new SubProgressMonitor(monitor, 0), uiInfo, false);
                     source.copy(destinationPath, IResource.SHALLOW,
                             new SubProgressMonitor(monitor, 1));
                     // Record the copy
 resourcesAtDestination.add(getWorkspace().getRoot()
                             .findMember(destinationPath));
                     for (int j = 0; j < deleted.length; j++) {
                         overwrittenResources.add(deleted[j]);
                     }
                 }
             } else {
                 if (existing != null) {
                     if (source.isLinked() == existing.isLinked()) {
                         overwrittenResources.add(copyOverExistingResource(
                                 source, existing, new SubProgressMonitor(
                                         monitor, 1), uiInfo, false));
                         // Record the "copy"
 resourcesAtDestination.add(existing);
                     } else {
                         // Copying a linked resource over unlinked or vice
 // versa. Can't use setContents here. Fixes bug 28772.
 ResourceDescription[] deleted = delete(
                                 new IResource[] { existing },
                                 new SubProgressMonitor(monitor, 0), uiInfo,
                                 false);
                         source.copy(destinationPath, IResource.SHALLOW,
                                 new SubProgressMonitor(monitor, 1));
                         // Record the copy
 resourcesAtDestination.add(getWorkspace().getRoot()
                                 .findMember(destinationPath));
                         for (int j = 0; j < deleted.length; j++) {
                             overwrittenResources.add(deleted[j]);
                         }
                     }
                 } else {
                     // no resources are being overwritten
 // ensure the destination path exists
 IPath parentPath = destination;
                     if (pathIncludesName) {
                         parentPath = destination.removeLastSegments(1);
                     }
                     IContainer generatedParent = generateContainers(parentPath);
                     source.copy(destinationPath, IResource.SHALLOW,
                             new SubProgressMonitor(monitor, 1));
                     // Record the copy. If we had to generate a parent
 // folder, that should be recorded as part of the copy
 if (generatedParent == null) {
                         resourcesAtDestination.add(getWorkspace().getRoot()
                                 .findMember(destinationPath));
                     } else {
                         resourcesAtDestination.add(generatedParent);
                     }
                 }

                 if (monitor.isCanceled()) {
                     throw new OperationCanceledException();
                 }
             }
         }
         monitor.done();
         return (ResourceDescription[]) overwrittenResources
                 .toArray(new ResourceDescription[overwrittenResources.size()]);

     }

     /**
      * Moves the resources to the given destination. This method can be called
      * recursively to merge folders during folder move.
      *
      * @param resources
      * the resources to be moved
      * @param destination
      * the destination path for the resources, relative to the
      * workspace
      * @param resourcesAtDestination
      * A list used to record each moved resource.
      * @param reverseDestinations
      * A list used to record each moved resource's original location
      * @param monitor
      * the progress monitor used to show progress
      * @param uiInfo
      * the IAdaptable (or <code>null</code>) provided by the
      * caller in order to supply UI information for prompting the
      * user if necessary. When this parameter is not
      * <code>null</code>, it contains an adapter for the
      * org.eclipse.swt.widgets.Shell.class
      * @param pathIncludesName
      * a boolean that indicates whether the specified path includes
      * the resource's name at the destination. If this value is
      * <code>true</code>, the destination will contain the desired
      * name of the resource (usually only desired when only one
      * resource is being moved). If this value is <code>false</code>,
      * each resource's name will be appended to the destination.
      * @return an array of ResourceDescriptions describing any resources that
      * were overwritten by the move operation
      * @throws CoreException
      * propagates any CoreExceptions thrown from the resources API
      */
     static ResourceDescription[] move(IResource[] resources, IPath destination,
             List resourcesAtDestination, List reverseDestinations,
             IProgressMonitor monitor, IAdaptable uiInfo,
             boolean pathIncludesName) throws CoreException {

         monitor.beginTask("", resources.length); //$NON-NLS-1$
 monitor
                 .setTaskName(UndoMessages.AbstractResourcesOperation_MovingResources);
         List overwrittenResources = new ArrayList ();
         for (int i = 0; i < resources.length; i++) {
             IResource source = resources[i];
             IPath destinationPath;
             if (pathIncludesName) {
                 destinationPath = destination;
             } else {
                 destinationPath = destination.append(source.getName());
             }
             IWorkspaceRoot workspaceRoot = getWorkspaceRoot();
             IResource existing = workspaceRoot.findMember(destinationPath);
             if (source.getType() == IResource.FOLDER && existing != null) {
                 // The resource is a folder and it exists in the destination.
 // Move its children to the existing destination.
 if (source.isLinked() == existing.isLinked()) {
                     IResource[] children = ((IContainer) source).members();
                     ResourceDescription[] overwritten = move(children,
                             destinationPath, resourcesAtDestination,
                             reverseDestinations, new SubProgressMonitor(
                                     monitor, 1), uiInfo, false);
                     // We don't record the moved resources since the recursive
 // call has done so. Just record the overwrites.
 for (int j = 0; j < overwritten.length; j++) {
                         overwrittenResources.add(overwritten[j]);
                     }
                     // Delete the source. No need to record it since it
 // will get moved back.
 delete(source, monitor, uiInfo, false, false);
                 } else {
                     // delete the destination folder, moving a linked folder
 // over an unlinked one or vice versa. Fixes bug 28772.
 ResourceDescription[] deleted = delete(
                             new IResource[] { existing },
                             new SubProgressMonitor(monitor, 0), uiInfo, false);
                     // Record the original path
 reverseDestinations.add(source.getFullPath());
                     source.move(destinationPath, IResource.SHALLOW
                             | IResource.KEEP_HISTORY, new SubProgressMonitor(
                             monitor, 1));
                     // Record the resource at its destination
 resourcesAtDestination.add(getWorkspace().getRoot()
                             .findMember(destinationPath));
                     for (int j = 0; j < deleted.length; j++) {
                         overwrittenResources.add(deleted[j]);
                     }
                 }
             } else {
                 if (existing != null) {
                     if (source.isLinked() == existing.isLinked()) {
                         // Record the original path
 reverseDestinations.add(source.getFullPath());
                         overwrittenResources.add(copyOverExistingResource(
                                 source, existing, new SubProgressMonitor(
                                         monitor, 1), uiInfo, true));
                         resourcesAtDestination.add(existing);
                     } else {
                         // Moving a linked resource over unlinked or vice
 // versa. Can't use setContents here. Fixes bug 28772.
 ResourceDescription[] deleted = delete(
                                 new IResource[] { existing },
                                 new SubProgressMonitor(monitor, 0), uiInfo,
                                 false);
                         reverseDestinations.add(source.getFullPath());
                         source.move(destinationPath, IResource.SHALLOW
                                 | IResource.KEEP_HISTORY,
                                 new SubProgressMonitor(monitor, 1));
                         // Record the resource at its destination
 resourcesAtDestination.add(getWorkspace().getRoot()
                                 .findMember(destinationPath));
                         for (int j = 0; j < deleted.length; j++) {
                             overwrittenResources.add(deleted[j]);
                         }
                     }
                 } else {
                     // No resources are being overwritten.
 // First record the source path
 reverseDestinations.add(source.getFullPath());
                     // ensure the destination path exists
 IPath parentPath = destination;
                     if (pathIncludesName) {
                         parentPath = destination.removeLastSegments(1);
                     }

                     IContainer generatedParent = generateContainers(parentPath);
                     source.move(destinationPath, IResource.SHALLOW
                             | IResource.KEEP_HISTORY, new SubProgressMonitor(
                             monitor, 1));
                     // Record the move. If we had to generate a parent
 // folder, that should be recorded as part of the copy
 if (generatedParent == null) {
                         resourcesAtDestination.add(getWorkspace().getRoot()
                                 .findMember(destinationPath));
                     } else {
                         resourcesAtDestination.add(generatedParent);
                     }
                 }

                 if (monitor.isCanceled()) {
                     throw new OperationCanceledException();
                 }
             }
         }
         monitor.done();
         return (ResourceDescription[]) overwrittenResources
                 .toArray(new ResourceDescription[overwrittenResources.size()]);

     }

     /**
      * Recreate the resources from the specified resource descriptions.
      *
      * @param resourcesToRecreate
      * the ResourceDescriptions describing resources to be recreated
      * @param monitor
      * the progress monitor used to show progress
      * @param uiInfo
      * the IAdaptable (or <code>null</code>) provided by the
      * caller in order to supply UI information for prompting the
      * user if necessary. When this parameter is not
      * <code>null</code>, it contains an adapter for the
      * org.eclipse.swt.widgets.Shell.class
      * @return an array of resources that were created
      * @throws CoreException
      * propagates any CoreExceptions thrown from the resources API
      */
     static IResource[] recreate(ResourceDescription[] resourcesToRecreate,
             IProgressMonitor monitor, IAdaptable uiInfo) throws CoreException {
         final List exceptions = new ArrayList ();
         IResource[] resourcesToReturn = new IResource[resourcesToRecreate.length];
         monitor.beginTask("", resourcesToRecreate.length); //$NON-NLS-1$
 monitor
                 .setTaskName(UndoMessages.AbstractResourcesOperation_CreateResourcesProgress);
         try {
             for (int i = 0; i < resourcesToRecreate.length; ++i) {
                 if (monitor.isCanceled()) {
                     throw new OperationCanceledException();
                 }
                 try {
                     resourcesToReturn[i] = resourcesToRecreate[i]
                             .createResource(new SubProgressMonitor(monitor, 1));
                 } catch (CoreException e) {
                     exceptions.add(e);
                 }
             }
             IStatus result = WorkspaceUndoUtil.createResult(exceptions);
             if (!result.isOK()) {
                 throw new CoreException(result);
             }
         } finally {
             monitor.done();
         }
         return resourcesToReturn;
     }

     /**
      * Delete the specified resources, returning a resource description that can
      * be used to restore it.
      *
      * @param resourceToDelete
      * the resource to be deleted
      * @param monitor
      * the progress monitor to use to show the operation's progress
      * @param uiInfo
      * the IAdaptable (or <code>null</code>) provided by the
      * caller in order to supply UI information for prompting the
      * user if necessary. When this parameter is not
      * <code>null</code>, it contains an adapter for the
      * org.eclipse.swt.widgets.Shell.class
      * @param forceOutOfSyncDelete
      * a boolean indicating whether a resource should be deleted even
      * if it is out of sync with the file system
      * @param deleteContent
      * a boolean indicating whether project content should be deleted
      * when a project resource is to be deleted
      * @return a ResourceDescription that can be used to restore the deleted
      * resource.
      * @throws CoreException
      * propagates any CoreExceptions thrown from the resources API
      */
     static ResourceDescription delete(IResource resourceToDelete,
             IProgressMonitor monitor, IAdaptable uiInfo,
             boolean forceOutOfSyncDelete, boolean deleteContent)
             throws CoreException {
         ResourceDescription resourceDescription = ResourceDescription
                 .fromResource(resourceToDelete);
         if (resourceToDelete.getType() == IResource.PROJECT) {
             // it is a project
 monitor
                     .setTaskName(UndoMessages.AbstractResourcesOperation_DeleteResourcesProgress);
             IProject project = (IProject) resourceToDelete;
             project.delete(deleteContent, forceOutOfSyncDelete, monitor);
         } else {
             // if it's not a project, just delete it
 monitor.beginTask("", 2); //$NON-NLS-1$
 monitor
                     .setTaskName(UndoMessages.AbstractResourcesOperation_DeleteResourcesProgress);
             int updateFlags;
             if (forceOutOfSyncDelete) {
                 updateFlags = IResource.KEEP_HISTORY | IResource.FORCE;
             } else {
                 updateFlags = IResource.KEEP_HISTORY;
             }
             resourceToDelete.delete(updateFlags, new SubProgressMonitor(
                     monitor, 1));
             resourceDescription.recordStateFromHistory(resourceToDelete,
                     new SubProgressMonitor(monitor, 1));
             monitor.done();
         }

         return resourceDescription;
     }

     /*
      * Copy the content of the specified resource to the existing resource,
      * returning a ResourceDescription that can be used to restore the original
      * content. Do nothing if the resources are not files.
      */
     private static ResourceDescription copyOverExistingResource(
             IResource source, IResource existing, IProgressMonitor monitor,
             IAdaptable uiInfo, boolean deleteSourceFile) throws CoreException {
         if (!(source instanceof IFile && existing instanceof IFile)) {
             return null;
         }
         IFile file = (IFile) source;
         IFile existingFile = (IFile) existing;
         monitor
                 .beginTask(
                         UndoMessages.AbstractResourcesOperation_CopyingResourcesProgress,
                         3);
         if (file != null && existingFile != null) {
             if (validateEdit(file, existingFile, getShell(uiInfo))) {
                 // Remember the state of the existing file so it can be
 // restored.
 FileDescription fileDescription = new FileDescription(
                         existingFile);
                 // Reset the contents to that of the file being moved
 existingFile.setContents(file.getContents(),
                         IResource.KEEP_HISTORY, new SubProgressMonitor(monitor,
                                 1));
                 fileDescription.recordStateFromHistory(existingFile,
                         new SubProgressMonitor(monitor, 1));
                 // Now delete the source file if requested
 // We don't need to remember anything about it, because
 // any undo involving this operation will move the original
 // content back to it.
 if (deleteSourceFile) {
                     file.delete(IResource.KEEP_HISTORY, new SubProgressMonitor(
                             monitor, 1));
                 }
                 monitor.done();
                 return fileDescription;
             }
         }
         monitor.done();
         return null;
     }

     /*
      * Check for existence of the specified path and generate any containers
      * that do not yet exist. Return any generated containers, or null if no
      * container had to be generated.
      */
     private static IContainer generateContainers(IPath path)
             throws CoreException {
         IResource container;
         if (path.segmentCount() == 0) {
             // nothing to generate
 return null;
         }
         container = getWorkspaceRoot().findMember(path);
         // Nothing to generate because container exists
 if (container != null) {
             return null;
         }

         // Now make a non-existent handle representing the desired container
 if (path.segmentCount() == 1) {
             container = ResourcesPlugin.getWorkspace().getRoot().getProject(
                     path.segment(0));
         } else {
             container = ResourcesPlugin.getWorkspace().getRoot()
                     .getFolder(path);
         }
         ContainerDescription containerDescription = ContainerDescription
                 .fromContainer((IContainer) container);
         container = containerDescription.createResourceHandle();
         containerDescription.createExistentResourceFromHandle(container,
                 new NullProgressMonitor());
         return (IContainer) container;
     }

     /*
      * Ask the user whether the given resource should be deleted despite being
      * out of sync with the file system.
      *
      * Return one of the IDialogConstants constants indicating which of the Yes,
      * Yes to All, No, Cancel options has been selected by the user.
      */
     private static int queryDeleteOutOfSync(IResource resource,
             IAdaptable uiInfo) {
         Shell shell = getShell(uiInfo);
         final MessageDialog dialog = new MessageDialog(
                 shell,
                 UndoMessages.AbstractResourcesOperation_deletionMessageTitle,
                 null,
                 NLS
                         .bind(
                                 UndoMessages.AbstractResourcesOperation_outOfSyncQuestion,
                                 resource.getName()), MessageDialog.QUESTION,
                 new String [] { IDialogConstants.YES_LABEL,
                         IDialogConstants.YES_TO_ALL_LABEL,
                         IDialogConstants.NO_LABEL,
                         IDialogConstants.CANCEL_LABEL }, 0);
         shell.getDisplay().syncExec(new Runnable () {
             public void run() {
                 dialog.open();
             }
         });
         int result = dialog.getReturnCode();
         if (result == 0) {
             return IDialogConstants.YES_ID;
         }
         if (result == 1) {
             return IDialogConstants.YES_TO_ALL_ID;
         }
         if (result == 2) {
             return IDialogConstants.NO_ID;
         }
         return IDialogConstants.CANCEL_ID;
     }

     /*
      * Creates and return a result status appropriate for the given list of
      * exceptions.
      */
     private static IStatus createResult(List exceptions) {
         if (exceptions.isEmpty()) {
             return Status.OK_STATUS;
         }
         final int exceptionCount = exceptions.size();
         if (exceptionCount == 1) {
             return ((CoreException) exceptions.get(0)).getStatus();
         }
         CoreException[] children = (CoreException[]) exceptions
                 .toArray(new CoreException[exceptionCount]);
         boolean outOfSync = false;
         for (int i = 0; i < children.length; i++) {
             if (children[i].getStatus().getCode() == IResourceStatus.OUT_OF_SYNC_LOCAL) {
                 outOfSync = true;
                 break;
             }
         }
         String title = outOfSync ? UndoMessages.AbstractResourcesOperation_outOfSyncError
                 : UndoMessages.AbstractResourcesOperation_deletionExceptionMessage;
         final MultiStatus multi = new MultiStatus(
                 IDEWorkbenchPlugin.IDE_WORKBENCH, 0, title, null);
         for (int i = 0; i < exceptionCount; i++) {
             CoreException exception = children[i];
             IStatus status = exception.getStatus();
             multi.add(new Status(status.getSeverity(), status.getPlugin(),
                     status.getCode(), status.getMessage(), exception));
         }
         return multi;
     }

     /*
      * Return the workspace.
      */
     private static IWorkspace getWorkspace() {
         return ResourcesPlugin.getWorkspace();
     }

     /*
      * Return the workspace root.
      */
     private static IWorkspaceRoot getWorkspaceRoot() {
         return getWorkspace().getRoot();
     }

     /*
      * Validate the destination file if it is read-only and additionally the
      * source file if both are read-only. Returns true if both files could be
      * made writeable.
      */
     private static boolean validateEdit(IFile source, IFile destination,
             Shell shell) {
         if (destination.isReadOnly()) {
             IWorkspace workspace = WorkspaceUndoUtil.getWorkspace();
             IStatus status;
             if (source.isReadOnly()) {
                 status = workspace.validateEdit(new IFile[] { source,
                         destination }, shell);
             } else {
                 status = workspace.validateEdit(new IFile[] { destination },
                         shell);
             }
             return status.isOK();
         }
         return true;
     }

     /**
      * Return the shell described by the specified adaptable, or the active
      * shell if no shell has been specified in the adaptable.
      *
      * @param uiInfo
      * the IAdaptable (or <code>null</code>) provided by the
      * caller in order to supply UI information for prompting the
      * user if necessary. When this parameter is not
      * <code>null</code>, it contains an adapter for the
      * org.eclipse.swt.widgets.Shell.class
      *
      * @return the Shell that can be used to show information
      */
     public static Shell getShell(IAdaptable uiInfo) {
         if (uiInfo != null) {
             Shell shell = (Shell) uiInfo.getAdapter(Shell.class);
             if (shell != null) {
                 return shell;
             }
         }
         return PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell();
     }
 }

